//
// Created by ljy on 10/24/21.
//
#include <linux/init.h>
#include <asm-i386/page.h>
#include <linux/bootmem.h>
#include <linux/multiboot.h>
#include <asm-i386/e820.h>
#include <asm-i386/pgtable.h>
#define PFN_UP(x)	(((x) + PAGE_SIZE-1) >> PAGE_SHIFT)
#define PFN_DOWN(x)	((x) >> PAGE_SHIFT)
#define PFN_PHYS(x)	((x) << PAGE_SHIFT)
extern char _text, _etext, _edata, _end, _start;
/*
 * Reserved space for vmalloc and iomap - defined in asm/page.h
 */
#define MAXMEM_PFN	PFN_DOWN(MAXMEM)
#define MAX_NONPAE_PFN	(1 << 20)
struct e820map e820;
/*
 * Find the highest page frame number we have available
 */
static void find_max_pfn(void)
{
    int count = 0;
    mmap_entry_t *mmap_start_addr = (mmap_entry_t *)&glb_mboot_ptr->mmap_addr;
    mmap_entry_t *mmap_end_addr = (mmap_entry_t *)&glb_mboot_ptr->mmap_addr + glb_mboot_ptr->mmap_length;
    mmap_entry_t *map_entry;

    max_pfn = 0;
    for (map_entry = mmap_start_addr; map_entry < mmap_end_addr; map_entry++) {
        unsigned long start, end;
        e820.map[count].addr = map_entry->base_addr_low;
        e820.map[count].size = map_entry->length_low;
        e820.map[count].type = map_entry->type;
        count++;
        /* RAM? */
        if (map_entry->type != E820_RAM)
            continue;
        start = PFN_UP(map_entry->base_addr_low);
        end = PFN_DOWN(map_entry->base_addr_low+ map_entry->length_low);
        if (start >= end)
            continue;
        if (end > max_pfn)
            max_pfn = end;
    }
    e820.nr_map = count;
}

/*
 * Determine low and high memory ranges:
 */
static unsigned long find_max_low_pfn(void)
{
    unsigned long max_low_pfn;

    max_low_pfn = max_pfn;
    if (max_low_pfn > MAXMEM_PFN) {
        max_low_pfn = MAXMEM_PFN;
    }
    return max_low_pfn;
}

static void register_bootmem_low_pages(unsigned long max_low_pfn)
{
    int i;

    for (i = 0; i < e820.nr_map; i++) {
        unsigned long curr_pfn, last_pfn, size;
        /*
         * Reserve usable low memory
         */
        if (e820.map[i].type != E820_RAM)
            continue;
        /*
         * We are rounding up the start address of usable memory:
         */
        curr_pfn = PFN_UP(e820.map[i].addr);
        if (curr_pfn >= max_low_pfn)
            continue;
        /*
         * ... and at the end of the usable range downwards:
         */
        last_pfn = PFN_DOWN(e820.map[i].addr + e820.map[i].size);

        if (last_pfn > max_low_pfn)
            last_pfn = max_low_pfn;
        /*
         * .. finally, did all the rounding and playing
         * around just make the area go away?
         */
        if (last_pfn <= curr_pfn)
            continue;
        size = last_pfn - curr_pfn;
        free_bootmem(PFN_PHYS(curr_pfn), PFN_PHYS(size));
    }
}

static unsigned long setup_memory(void)
{
    unsigned long bootmap_size, start_pfn;
    /*
     * partially used pages are not usable - thus
     * we are rounding upwards:
     */
    start_pfn = PFN_UP(__pa(&_end));//第一块可以被用到的物理页面帧的偏移。
    find_max_pfn();//遍历e820图，查找最高的可用PFN,(e820图是操作系统引导程序提供物理内存信息的结构)
    max_low_pfn = find_max_low_pfn();//在ZONE_NORMAL中找到可寻址的最高页面帧
    /*
     * Initialize the boot-time allocator (with low memory only):
     */
    bootmap_size = init_bootmem(start_pfn, max_low_pfn);
    register_bootmem_low_pages(max_low_pfn);
    /*
     * Reserve the bootmem bitmap itself as well. We do this in two
     * steps (first step was init_bootmem()) because this catches
     * the (very unlikely) case of us accidentally initializing the
     * bootmem allocator with an invalid RAM area.
     */
    reserve_bootmem(HIGH_MEMORY, (PFN_PHYS(start_pfn) +
                                  bootmap_size + PAGE_SIZE-1) - (HIGH_MEMORY));
    /*
     * reserve physical page 0 - it's a special BIOS page on many boxes,
     * enabling clean reboots, SMP operation, laptop functions.
     */
    reserve_bootmem(0, PAGE_SIZE);
    return max_low_pfn;
}


void setup_arch(void)
{

    setup_memory();
    paging_init();
    //register_memory(max_low_pfn);
}